import {PublicKey} from '@solana/web3.js';
import {TOKEN_PROGRAM_ID, getTokenMetadata} from '@solana/spl-token';
import {useSolanaClientProvider} from './useSolanaClientProvider';
import {useSolanaTokenListProvider} from './useSolanaTokenListProvider';
import {
  createContext,
  useCallback,
  useContext,
  useState,
  ReactNode,
  useMemo,
} from 'react';
import {UserToken} from '../../db/models/userTokenModel';
import {SolanaTokenMetadata} from '../../types';
import {parseValue} from '../../utils';
import {useAccountProvider} from '../useAccountProvider';

export async function fetchTokenRicherMetadata(uri: string): Promise<any> {
  const response = await fetch(uri, {
    method: 'GET',
  });
  if (response.status == 200) {
    return await response.json();
  } else {
    return null;
  }
}

export type SolanaOwnerTokenAccountsContextType = {
  accountsloading: boolean;
  userTokens: UserToken[];
  setAccountsLoading: (value: boolean) => void;
  getTokenAccounts: () => Promise<void>;
};

export const SolanaOwnerTokenAccountsContext = createContext<
  SolanaOwnerTokenAccountsContextType | undefined
>(undefined);

export const useSolanaOwnerTokenAccountsProvider =
  (): SolanaOwnerTokenAccountsContextType => {
    const context = useContext(SolanaOwnerTokenAccountsContext);
    if (!context) {
      throw new Error(
        'useSolanaOwnerTokenAccountsProvider must be used within an SolanaOwnerTokenAccountsProvider',
      );
    }
    return context;
  };

export function SolanaOwnerTokenAccountsProvider({
  children,
}: Readonly<{
  children: ReactNode;
}>) {
  const [accountsloading, setAccountsLoading] = useState<boolean>(true);
  const [userTokens, setUserTokens] = useState<UserToken[]>([]);
  const {solanaClient, chainId} = useSolanaClientProvider();
  const {solanaTokenList} = useSolanaTokenListProvider();
  const {userInfo} = useAccountProvider();

  const getTokenAccounts = useCallback(async () => {
    setAccountsLoading(true);
    try {
      const address = new PublicKey(userInfo!.solanaAddress);

      const data = await solanaClient!.getParsedTokenAccountsByOwner(address, {
        programId: TOKEN_PROGRAM_ID,
      });
      const _userTokens: UserToken[] = [];
      for (let i = 0; i < data.value.length; i++) {
        const parsedAccount = data.value[0];

        const mint = parsedAccount.account.data.parsed.info.mint;
        const owner = parsedAccount.account.data.parsed.info.owner;

        let userToken: UserToken;
        const tokenMetadataList = solanaTokenList.filter(item => {
          return (
            item.address.toLowerCase() == mint.toLowerCase() &&
            item.wrappedChainId == chainId
          );
        });
        if (tokenMetadataList.length > 0) {
          const tokenMetadata: SolanaTokenMetadata = tokenMetadataList[0];
          userToken = {
            name: tokenMetadata.name,
            symbol: tokenMetadata.symbol,
            decimals:
              parsedAccount.account.data.parsed.info.tokenAmount.decimals,
            address: mint,
            logo: tokenMetadata.logoURI,
            owner: owner,
            amount: parsedAccount.account.data.parsed.info.tokenAmount.amount,
            uiAmount:
              parsedAccount.account.data.parsed.info.tokenAmount.uiAmountString,
            chainId: chainId,
            type: 'spl',
          };
        } else {
          const tokenMetadata = await getTokenMetadata(
            solanaClient!,
            new PublicKey(parsedAccount.account.data.parsed.info.mint),
          ).catch(error => {
            return null;
          });
          if (tokenMetadata != null) {
            const richerData = await fetchTokenRicherMetadata(
              tokenMetadata.uri,
            );
            userToken = {
              name: tokenMetadata.name,
              symbol: tokenMetadata.symbol,
              decimals:
                parsedAccount.account.data.parsed.info.tokenAmount.decimals,
              address: mint,
              owner: owner,
              logo: richerData != null ? richerData['image'] : null,
              amount: parsedAccount.account.data.parsed.info.tokenAmount.amount,
              uiAmount:
                parsedAccount.account.data.parsed.info.tokenAmount
                  .uiAmountString,
              chainId: chainId,
              type: 'spl',
            };
          } else {
            userToken = {
              name: 'Unknown Token',
              symbol: 'Unknown',
              decimals:
                parsedAccount.account.data.parsed.info.tokenAmount.decimals,
              address: mint,
              owner: owner,
              amount: parsedAccount.account.data.parsed.info.tokenAmount.amount,
              uiAmount:
                parsedAccount.account.data.parsed.info.tokenAmount
                  .uiAmountString,
              chainId: chainId,
              type: 'spl',
            };
          }
        }

        _userTokens.push(userToken);
      }

      const balance = await solanaClient!.getBalance(address);
      const uiBalance = parseValue(balance.toString(), 9);

      const userNativeToken: UserToken = {
        name: 'Solana',
        symbol: 'SOL',
        decimals: 9,
        logo: 'https://cdn.jsdelivr.net/gh/trustwallet/assets@master/blockchains/solana/info/logo.png',
        type: 'native',
        amount: balance + '',
        uiAmount: uiBalance.toString(),
        address: '',
        owner: userInfo!.solanaAddress,
        chainId: chainId,
      };

      const rUserTokens = [userNativeToken].concat(_userTokens);
      console.log('rUserTokens', rUserTokens);
      setUserTokens(rUserTokens);
    } catch (error) {
      console.log('getTokenAccounts', error);
    }
    setAccountsLoading(false);
  }, [solanaClient, userInfo, solanaTokenList]);

  const value = useMemo(
    () => ({
      accountsloading,
      setAccountsLoading,
      getTokenAccounts,
      userTokens,
    }),
    [accountsloading, setAccountsLoading, getTokenAccounts, userTokens],
  );

  return (
    <SolanaOwnerTokenAccountsContext.Provider value={value}>
      {children}
    </SolanaOwnerTokenAccountsContext.Provider>
  );
}
